/* eslint-disable @typescript-eslint/no-explicit-any */
// mem pool
// 脚本必须用pool才能控制内存！
// https://www.html5rocks.com/en/tutorials/speed/static-mem-pools/

// from wxgame pool.js

/**
 * 简易的对象池实现, 使用的前提是必须明确的知道对象的生命周期，在结束时push回pool
 * 用于对象的存贮和重复使用
 * 可以有效减少对象创建开销和避免频繁的垃圾回收
 * 提高游戏性能
 * 1，适用于状态少，简单的对象比如timer，msg
 * 2. 用于同类型的系统对象（无法为之实现pool_init），例如人物影子，例如地图块（大小都一样）
 * 3. 父亲的子对象列表，比如msgbox的msgline
 * 4. 一批一批的创建和回收，比如war在结束统一回收所有对象push_allrunning
 * 5. 最好Pool的object用二段式构造, 都不直接使用new来调用，而是CreateXXX()
 * CreateC(a,b) { c = new C; c.init(a,b); } 并且不写C的construct()
 * 这样只用简单替换 new C ==> Pool.ins().pop(C) 就实现了的对象池 ，而不必为之写pool_init
 */

// import { strict as assert } from 'assert';

interface PoolObjectInterface {
	poolInit(...args: any[]): void;
}

class PoolObject {
	// 复杂对象不使用pool的对象池的功能
	// 但是pool仍然具有管理的功能，同时可以检测内存泄露
	disablePool(): boolean {
		return false;
	}

	className(): string {
		return this.constructor.name;
	}

	_poolkey = '';

	_name = '';

	_newindex = 0;

	newIndex(index: number, _name: string): void {
		this._newindex = index;
		// console.log("PoolObject.new_index",index,name);
	}

	// 为了调试方便并且查错！
	_wherepool = '';

	_inpool = false;

	inPool(): boolean {
		return this._inpool;
	}

	isReleased(): boolean {
		return this.inPool();
	}

	release(): void {
		if (this.inPool()) {
			console.error(`error! release but _inpool${this._name}`);
			assert(0);
			return;
		}
		// eslint-disable-next-line no-use-before-define
		Pool.ins().push(this);
		this.onReleased();
	}

	onReleased(): void {}

	// override funcs...
	poolInit(..._args: any[]): void {}

	dump(_running: boolean): void {}
}

class PoolObjects extends PoolObject {
	objs: PoolObject[] = [];

	length(): number {
		return this.objs.length;
	}

	remove(name: string): PoolObject {
		const obj = this.objs.removeIfa((T: PoolObject) => T._name === name);
		if (obj) obj.release();
		return obj;
	}

	release(): void {
		this.releaseObjs();
		super.release();
	}

	releaseObjs(): void {
		for (const obj of this.objs) obj.release();
		this.objs.length = 0;
	}

	dump(running: boolean): void {
		console.log(`${this.className()}.dump`, this._name, this.length());
		for (const t of this.objs) t.dump(running);
	}
}

const funcp: any = Function.prototype;

if (!funcp.construct) {
	funcp.construct = function funcConstruct(argArray: any[]) {
		assert(Array.isArray(argArray));
		// const constr = this;
		const NullaryFunc = Function.prototype.bind.apply(
			this,
			// <any>[null].concat(argArray),
			[null, ...argArray],
		);
		return new NullaryFunc();
	};
}

class PoolItem {
	constructor(key: string) {
		this.key = key;
	}

	key: string;

	pool: any[] = [];

	running: any[] = [];

	newobj = 0;

	popobj = 0;

	pushAllrunning(): void {
		for (const obj of this.running) obj._inpool = true;
		this.pool = this.pool.concat(this.running);
		this.running = [];
	}

	dumpStr(): string {
		return `Pool.dump ${this.key} new:${this.newobj} - pop:${this.popobj} - pool:${this.pool.length} - running:${this.running.length}`;
	}

	tryPop(): any {
		if (this.pool.length > 0) {
			const instance = this.pool.pop();
			assert(instance._inpool);
			instance._inpool = false;
			return instance;
		}
		return null;
	}

	pop(where: string, name: string, cls: any, ...args: any[]): any {
		let rt = this.tryPop();
		if (rt) {
			if (rt.poolInit) rt.poolInit(...args);
			// cls.apply(rt,args); 会报错！
			this.popobj += 1;
		} else {
			rt = cls.construct(args);
			if (rt.newIndex) rt.newIndex(this.newobj++, name);
			else rt._newindex = this.newobj++;
		}

		rt._poolkey = this.key;
		rt._inpool = false;
		rt._wherepool = where;
		this.running.push(rt);

		return rt;
	}

	pushSys(instance: any): void {
		if (instance._inpool) {
			console.error('Poool.pushSys error! instance._inpool', instance);
			assert(0);
			return;
		}
		instance._inpool = true;

		if (!instance.disable_pool || !instance.disable_pool()) this.pool.push(instance);
		this.running.remove(instance);
	}

	forall(checkFunc: (o: unknown, runing: boolean) => void): void {
		for (const obj of this.running) checkFunc(obj, true);
		for (const obj of this.pool) checkFunc(obj, false);
	}

	forallRunning(checkFunc: (o: unknown) => void): void {
		for (const obj of this.running) checkFunc(obj);
	}
}

class Pool {
	pools: { [key: string]: PoolItem } = {};

	// 把所有running的置为pool，比如战斗
	pushRunning(key: string): void {
		const pool = this.getPool(key);
		pool.pushAllrunning();
	}

	pushAllrunning(): void {
		for (const v of Object.values(this.pools)) v.pushAllrunning();
	}

	dumpPool(key?: string): void {
		if (key) {
			console.log(this.getPool(key).dumpStr());
		} else {
			console.log('dump_pool key new - pop - pool - run');
			const lines = [];
			for (const v of Object.values(this.pools)) lines.push(v.dumpStr());
			lines.sort();
			console.log(lines.join('\n'));
		}
	}

	dumpName(key: string, name: string): void {
		const pool = this.getPool(key);
		if (!pool) {
			console.log(`not find pool key ${key}`);
			return;
		}

		let find = 0;
		pool.forall((t: PoolObject, running: boolean) => {
			if (t._name.indexOf(name) !== -1) {
				find++;
				t.dump(running);
			}
		});
		if (find === 0) {
			console.log(`not find pool key ${key} name ${name}`);
		}
	}

	/**
	 * 根据对象标识符
	 * 获取对应的对象池
	 */
	getPool(name: string): PoolItem {
		if (!this.pools[name]) this.pools[name] = new PoolItem(name);
		return this.pools[name];
	}

	/**
	 * 根据传入的对象标识符，查询对象池
	 * 对象池为空创建新的类，否则从对象池中取
	 */
	// where for debug!
	pop(where: string, name: string, cls: any, ...args: any[]): any {
		const pool = this.getPool(name);
		// Array.prototype.splice.call(arguments,1,1) //remove name...
		return pool.pop(where, name, cls, ...args);
	}

	/**
	 * 将对象回收到对象池
	 * 方便后续继续使用
	 */
	push(instance: PoolObjectInterface): void {
		this.pushSys(instance);
	}

	pushSys(instance: any): void {
		const name = instance._poolkey;
		if (!name) {
			console.warn('Pool.pushSys warn _poolkey = nil', instance);
			return;
		}
		assert(name);
		this.getPool(name).pushSys(instance);
	}

	poolUsage(): { [key: string]: number[] } {
		const usage: { [key: string]: number[] } = {};
		for (const [k, v] of Object.entries(this.pools)) {
			// const v = this.pools[k];
			usage[k] = [v.pool.length, v.running.length, v.newobj, v.popobj];
		}
		return usage;
	}

	private static instance: Pool;

	public static ins(): Pool {
		if (!this.instance) this.instance = new Pool();
		return this.instance;
	}
}
